package com.linkhub.oms.admin.modules.stock.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.linkhub.oms.admin.common.exception.ExceptionFactory;
import com.linkhub.oms.admin.modules.stock.domain.dto.ConfirmCombineProcessingDTO;
import com.linkhub.oms.admin.modules.stock.domain.dto.ConfirmOutboundDTO;
import com.linkhub.oms.admin.modules.stock.domain.dto.PreemptionDTO;
import com.linkhub.oms.admin.modules.stock.domain.dto.StockCheckDTO;
import com.linkhub.oms.admin.modules.stock.enums.BusinessType;
import com.linkhub.oms.admin.modules.stock.enums.ProductType;
import com.linkhub.oms.admin.modules.stock.exception.PreemptionFailException;
import com.linkhub.oms.common.util.CacheKeyAssist;
import com.linkhub.oms.common.util.QtBeanUtil;
import com.linkhub.oms.common.util.json.QtFastjsonUtil;
import com.linkhub.oms.component.redis.locker.DistributedLocker;
import com.linkhub.oms.dao.mapper.PreemptionStockFlowEntityMapper;
import com.linkhub.oms.dao.mpservice.IPreemptionStockFlowService;
import com.linkhub.oms.dao.mpservice.IProductStockService;
import com.linkhub.oms.entity.PreemptionStockFlowEntity;
import com.linkhub.oms.entity.ProductStockEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * @ClassName PreemptionStockFlowImpl
 * @Description TODO
 * @Author XieYu
 * @Date 2024/6/25 22:00
 * @Version 1.0
 */
@Slf4j
@Service
public class PreemptionStockBizServiceImpl {
    @Resource
    private IPreemptionStockFlowService preemptionStockFlowService;
    @Resource
    private PreemptionStockFlowEntityMapper preemptionStockFlowMapper;
    @Resource
    private IProductStockService productStockService;
    @Resource
    private DistributedLocker distributedLocker;
    @Resource
    private BatchBizServiceImpl batchBizService;


    @Transactional(rollbackFor = Exception.class)
    public int preemption(BusinessType businessType, PreemptionDTO preemptionDTO) {
        log.info("库存管理-订单预占-开始商品预占：{}", QtFastjsonUtil.toJSONString(preemptionDTO));
        // 检查之前是否已预占过
        List<PreemptionStockFlowEntity> preemptionStockFlowEntities = preemptionStockFlowService
                .list(preemptionDTO.getUserId(), preemptionDTO.getStashName(), preemptionDTO.getSkuId(),
                        preemptionDTO.getBusinessNo());
        if (CollUtil.isNotEmpty(preemptionStockFlowEntities)) {
            log.info("再次预占");
            preemptionStockFlowEntities.forEach(preemptionStockFlowEntity -> {
                preemptionDTO.setSkuQuantity(preemptionDTO.getSkuQuantity() - preemptionStockFlowEntity.getAmount());
            });
        }

        if (preemptionDTO.getSkuQuantity() <= 0) { // 之前已经全部预占成功了，不需要再预占
            log.warn("之前已全部预占成功，不再执行预占逻辑");
            return 0;
        }

        String lockKey = CacheKeyAssist.getPreemptionStockUpdatedLockKey(preemptionDTO.getUserId(),
                preemptionDTO.getSkuId(), preemptionDTO.getStashName());
        if (!distributedLocker.tryLock(lockKey)) {
            log.error("预占库存时获取锁失败");
            throw ExceptionFactory.getI18nBizException("stock.preemption.updated.concurrence.error");
        }

        try {
            Map<Long, Integer>  batchMap = batchBizService.preemption(preemptionDTO);
            if (MapUtil.isNotEmpty(batchMap)) {
                ProductStockEntity productStock = productStockService.get(preemptionDTO.getUserId(),
                        preemptionDTO.getStashName(), preemptionDTO.getSkuId());
                // 增加流水
                List<PreemptionStockFlowEntity> preemptionStockFlowEntityList = Lists.newArrayList();
                batchMap.forEach((batchId, preemptionAmount) -> {
                    PreemptionStockFlowEntity preemptionStockFlowEntity = new PreemptionStockFlowEntity();
                    QtBeanUtil.copyProperties(preemptionDTO, preemptionStockFlowEntity);
                    preemptionStockFlowEntity.setBusinessType(businessType.getCode());
                    preemptionStockFlowEntity.setBatchId(batchId);
                    preemptionStockFlowEntity.setAmount(preemptionAmount);
                    preemptionStockFlowEntity.setBeforeAmount(productStock.getPreemptionStock());
                    preemptionStockFlowEntity.setAfterAmount(productStock.getPreemptionStock() + preemptionAmount);
                    preemptionStockFlowEntityList.add(preemptionStockFlowEntity);
                    preemptionDTO.setSkuQuantity(preemptionDTO.getSkuQuantity() - preemptionAmount);
                    productStock.setPreemptionStock(productStock.getPreemptionStock() + preemptionAmount);
                });
                preemptionStockFlowService.saveBatch(preemptionStockFlowEntityList);
                // 改变预占数量
                productStockService.updateById(productStock);
            }
        } finally {
            distributedLocker.unlock(lockKey);
        }
        boolean success = preemptionDTO.getSkuQuantity() <= 0;
        log.info("库存管理-订单预占-商品预占结果：{}", success);
        return preemptionDTO.getSkuQuantity();  // 返回是否预占成功
    }

    @Transactional
    public List<String> releasePreemption(BusinessType businessType, String userId, String skuId, String stashName, String businessNo, int releaseAmount) {
        log.info("库存盘点-释放预占-开始：no: {}, {}，释放量：{}", businessNo, userId + ":" + skuId + ":" + stashName, releaseAmount);
        String lockKey = CacheKeyAssist.getPreemptionStockUpdatedLockKey(userId, skuId, stashName);
        if (!distributedLocker.tryLock(lockKey)) {
            log.error("库存盘点预占取消时获取锁失败");
            throw ExceptionFactory.getI18nBizException("stock.preemption.updated.concurrence.error");
        }
        List<String> releaseBusinessNos = Lists.newArrayList();
        try {
            ProductStockEntity productStock = productStockService.get(userId, stashName, skuId);
            List<String> preemptedBusinessNos = preemptionStockFlowMapper.getPreemptedBusinessNo(BusinessType.SELL.getCode(),
                    userId, skuId, stashName); // 当前正预占着的记录
            List<PreemptionStockFlowEntity> preemptedFlows = preemptionStockFlowMapper.getByBusinessNos(preemptedBusinessNos);
            PreemptionStockFlowEntity srcFlow = new PreemptionStockFlowEntity();
            srcFlow.setBusinessType(businessType.getCode());
            srcFlow.setUserId(userId);
            srcFlow.setSkuId(skuId);
            srcFlow.setStashName(stashName);
            int srcPreemptedAmount = productStock.getPreemptionStock();
            Map<Long, Integer> batchRemainingMap = Maps.newHashMap();
            List<PreemptionStockFlowEntity> newPreemptionFlows = Lists.newArrayList();
            for (PreemptionStockFlowEntity preemptionStockFlowEntity : preemptedFlows) { // 开始释放预占
                if (releaseAmount <= 0) {
                    break;
                }
                PreemptionStockFlowEntity targetFlow = new PreemptionStockFlowEntity();
                QtBeanUtil.copyProperties(srcFlow, targetFlow);
                targetFlow.setBatchId(preemptionStockFlowEntity.getBatchId());
                targetFlow.setBusinessNo(preemptionStockFlowEntity.getBusinessNo());
                if (preemptionStockFlowEntity.getAmount() > releaseAmount) {  // 当前已可以全部释放了
                    targetFlow.setAmount(-releaseAmount);
                    targetFlow.setBeforeAmount(srcPreemptedAmount);
                    targetFlow.setAfterAmount(srcPreemptedAmount - releaseAmount);
                    newPreemptionFlows.add(targetFlow);

                    PreemptionStockFlowEntity newFlow = new PreemptionStockFlowEntity();
                    QtBeanUtil.copyProperties(preemptionStockFlowEntity, newFlow, CopyOptions.create()
                            .setIgnoreProperties("id", "createTime", "updateTime"));
                    newFlow.setAmount(preemptionStockFlowEntity.getAmount() - releaseAmount);  // 剩余预占的
                    newPreemptionFlows.add(newFlow);
                    releaseAmount = releaseAmount - preemptionStockFlowEntity.getAmount();
                    srcPreemptedAmount = srcPreemptedAmount - releaseAmount;
                    batchRemainingMap.put(preemptionStockFlowEntity.getBatchId(), releaseAmount);
                } else {
                    targetFlow.setAmount(-preemptionStockFlowEntity.getAmount());
                    targetFlow.setBeforeAmount(srcPreemptedAmount);
                    targetFlow.setAfterAmount(srcPreemptedAmount - preemptionStockFlowEntity.getAmount());
                    newPreemptionFlows.add(targetFlow);
                    releaseAmount = releaseAmount - preemptionStockFlowEntity.getAmount();
                    srcPreemptedAmount = srcPreemptedAmount - preemptionStockFlowEntity.getAmount();
                    batchRemainingMap.put(preemptionStockFlowEntity.getBatchId(), preemptionStockFlowEntity.getAmount());
                }
                releaseBusinessNos.add(preemptionStockFlowEntity.getBusinessNo());
            }
            productStock.setPreemptionStock(srcPreemptedAmount);
            productStockService.updateById(productStock);
            preemptionStockFlowService.saveBatch(newPreemptionFlows);
            batchBizService.preemptionCancel(batchRemainingMap);
        } finally {
            distributedLocker.unlock(lockKey);
        }
        return releaseBusinessNos;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean orderCancel(PreemptionDTO preemptionDTO) {
        log.info("库存管理-订单预占取消-开始：{}", QtFastjsonUtil.toJSONString(preemptionDTO));

        // 检查之前是否已预占过
        List<PreemptionStockFlowEntity> preemptionStockFlowEntities = preemptionStockFlowService
                .list(preemptionDTO.getUserId(), preemptionDTO.getStashName(), preemptionDTO.getSkuId(),
                        BusinessType.SELL.getCode(), preemptionDTO.getBusinessNo());

        if (CollUtil.isEmpty(preemptionStockFlowEntities)) {
            log.info("库存管理-订单预占取消-未预占不需要取消");
            return true;
        }

        String lockKey = CacheKeyAssist.getPreemptionStockUpdatedLockKey(preemptionDTO.getUserId(),
                preemptionDTO.getSkuId(), preemptionDTO.getStashName());
        if (!distributedLocker.tryLock(lockKey)) {
            log.error("预占库存取消时获取锁失败");
            throw ExceptionFactory.getI18nBizException("stock.preemption.updated.concurrence.error");
        }

        try {
            ProductStockEntity productStock = productStockService.get(preemptionDTO.getUserId(),
                    preemptionDTO.getStashName(), preemptionDTO.getSkuId());
            // 增加流水
            List<PreemptionStockFlowEntity> preemptionStockFlowEntityList = Lists
                    .newArrayListWithCapacity(preemptionStockFlowEntities.size());
            Map<Long, Integer> batchRemainingMap = Maps.newHashMap();
            preemptionStockFlowEntities.forEach((preemptionStockFlowEntity) -> {
                PreemptionStockFlowEntity cancelPreemptionEntity = new PreemptionStockFlowEntity();
                QtBeanUtil.copyProperties(preemptionStockFlowEntity, cancelPreemptionEntity,
                        CopyOptions.create().setIgnoreProperties("id", "createTime", "updateTime"));
                cancelPreemptionEntity.setBusinessType(BusinessType.SELL.getCode());
                cancelPreemptionEntity.setAmount(-preemptionStockFlowEntity.getAmount());
                cancelPreemptionEntity.setBeforeAmount(productStock.getPreemptionStock());
                cancelPreemptionEntity.setAfterAmount(productStock.getPreemptionStock() - preemptionStockFlowEntity.getAmount());
                preemptionStockFlowEntityList.add(cancelPreemptionEntity);
                productStock.setPreemptionStock(productStock.getPreemptionStock() - preemptionStockFlowEntity.getAmount());
                batchRemainingMap.put(preemptionStockFlowEntity.getBatchId(), preemptionStockFlowEntity.getAmount());
            });
            preemptionStockFlowService.saveBatch(preemptionStockFlowEntityList);
            // 改变预占数量
            productStockService.updateById(productStock);
            // 余额变更
            batchBizService.preemptionCancel(batchRemainingMap);
        } finally {
            distributedLocker.unlock(lockKey);
        }

        log.info("库存管理-订单预占取消-成功");
        return true;
    }


    @Transactional(rollbackFor = Exception.class)
    public List<PreemptionStockFlowEntity> wmsOutboundConfirm(ConfirmOutboundDTO confirmOutboundDTO){
        log.info("库存管理-出库预占移除-开始：{}", QtFastjsonUtil.toJSONString(confirmOutboundDTO));
        List<PreemptionStockFlowEntity> preemptionStockFlowEntities = preemptionStockFlowService
                .list(confirmOutboundDTO.getUserId(), confirmOutboundDTO.getStashName(), confirmOutboundDTO.getSkuId(),
                        BusinessType.SELL.getCode(), confirmOutboundDTO.getBusinessNo());

        if (CollUtil.isEmpty(preemptionStockFlowEntities)) {
            log.error("库存管理-出库预占移除-未预占");
            // 理论上需要通知管理员了，暂抛出异常吧
            throw new PreemptionFailException();
        }

        String lockKey = CacheKeyAssist.getPreemptionStockUpdatedLockKey(confirmOutboundDTO.getUserId(),
                confirmOutboundDTO.getSkuId(), confirmOutboundDTO.getStashName());
        if (!distributedLocker.tryLock(lockKey)) {
            log.error("预占库存取消时获取锁失败");
            throw ExceptionFactory.getI18nBizException("stock.preemption.updated.concurrence.error");
        }

        try {
            ProductStockEntity productStock = productStockService.get(confirmOutboundDTO.getUserId(),
                    confirmOutboundDTO.getStashName(), confirmOutboundDTO.getSkuId());
            // 增加流水
            List<PreemptionStockFlowEntity> preemptionStockFlowEntityList = Lists
                    .newArrayListWithCapacity(preemptionStockFlowEntities.size());
            preemptionStockFlowEntities.forEach((preemptionStockFlowEntity) -> {
                PreemptionStockFlowEntity preemptionEntity = new PreemptionStockFlowEntity();
                QtBeanUtil.copyProperties(preemptionStockFlowEntity, preemptionEntity,
                        CopyOptions.create().setIgnoreProperties("id", "createTime", "updateTime"));
                preemptionEntity.setBusinessType(BusinessType.SELL.getCode());
                preemptionEntity.setAmount(-preemptionStockFlowEntity.getAmount());
                preemptionEntity.setBeforeAmount(productStock.getPreemptionStock());
                preemptionEntity.setAfterAmount(productStock.getPreemptionStock() - preemptionStockFlowEntity.getAmount());
                preemptionStockFlowEntityList.add(preemptionEntity);
                productStock.setPreemptionStock(productStock.getPreemptionStock() - preemptionStockFlowEntity.getAmount());
            });
            preemptionStockFlowService.saveBatch(preemptionStockFlowEntityList);
            // 改变预占数量
            productStockService.updateById(productStock);
        } finally {
            distributedLocker.unlock(lockKey);
        }

        log.info("库存管理-出库预占移除-结束");
        return preemptionStockFlowEntities; // 把预占流水记录返回，用于转换出库流水
    }

    @Transactional
    public List<PreemptionStockFlowEntity> wmsCombineProcessingConfirm(ConfirmCombineProcessingDTO confirmCombineProcessingDTO) {
        log.info("库存管理-组合加工确认预占流水-开始：{}", QtFastjsonUtil.toJSONString(confirmCombineProcessingDTO));
        List<ConfirmCombineProcessingDTO.Product> sourceProducts = confirmCombineProcessingDTO.getSourceProducts();

        List<PreemptionStockFlowEntity> result = Lists.newArrayList();
        for (ConfirmCombineProcessingDTO.Product sourceProduct : sourceProducts) {
            List<PreemptionStockFlowEntity> preemptionStockFlowEntities = preemptionStockFlowService
                    .list(confirmCombineProcessingDTO.getUserId(), confirmCombineProcessingDTO.getStashName(),
                            sourceProduct.getSkuId(), BusinessType.COMBINE_PROCESS.getCode(), confirmCombineProcessingDTO.getBusinessNo());


            if (CollUtil.isEmpty(preemptionStockFlowEntities)) {
                log.error("库存管理-组合加工确认预占流水-未预占");
                throw new PreemptionFailException();
            }

            String lockKey = CacheKeyAssist.getPreemptionStockUpdatedLockKey(confirmCombineProcessingDTO.getUserId(),
                    sourceProduct.getSkuId(), confirmCombineProcessingDTO.getStashName());
            if (!distributedLocker.tryLock(lockKey)) {
                log.error("组合加工预占库存流水时获取锁失败");
                throw ExceptionFactory.getI18nBizException("stock.preemption.updated.concurrence.error");
            }

            try {
                ProductStockEntity productStock = productStockService.get(confirmCombineProcessingDTO.getUserId(),
                        confirmCombineProcessingDTO.getStashName(), sourceProduct.getSkuId());
                // 增加流水
                List<PreemptionStockFlowEntity> preemptionStockFlowEntityList = Lists
                        .newArrayListWithCapacity(preemptionStockFlowEntities.size());
                preemptionStockFlowEntities.forEach((preemptionStockFlowEntity) -> {
                    PreemptionStockFlowEntity preemptionEntity = new PreemptionStockFlowEntity();
                    QtBeanUtil.copyProperties(preemptionStockFlowEntity, preemptionEntity,
                            CopyOptions.create().setIgnoreProperties("id", "createTime", "updateTime"));
                    preemptionEntity.setBusinessType(BusinessType.COMBINE_PROCESS.getCode());
                    preemptionEntity.setAmount(-preemptionStockFlowEntity.getAmount());
                    preemptionEntity.setBeforeAmount(productStock.getPreemptionStock());
                    preemptionEntity.setAfterAmount(productStock.getPreemptionStock() + preemptionStockFlowEntity.getAmount());
                    preemptionStockFlowEntityList.add(preemptionEntity);
                    productStock.setPreemptionStock(productStock.getPreemptionStock() + preemptionStockFlowEntity.getAmount());
                });
                preemptionStockFlowService.saveBatch(preemptionStockFlowEntityList);
                // 改变预占数量
                productStockService.updateById(productStock);
            } finally {
                distributedLocker.unlock(lockKey);
            }
            result.addAll(preemptionStockFlowEntities);
        }

        log.info("库存管理-组合加工确认预占流水-结束");
        return result; // 把预占流水记录返回，用于转换出库流水
    }
}
